/*!
**	 Color blending function implementation
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**	Copyright (c) 2012-2013 Carlos López
**	Copyright (c) 2015 Diego Barrios Romero
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifndef __SYNFIG_COLOR_COLORBLENDINGFUNCTIONS_H
#define __SYNFIG_COLOR_COLORBLENDINGFUNCTIONS_H

#define COLOR_EPSILON	(0.000001f)

#include <synfig/color.h>

namespace synfig
{

typedef Color(*blendfunc)(Color &, Color &, float);

template <class C>
C blendfunc_COMPOSITE(C &src, C &dest, float amount)
{

    float a_src = src.get_a() * amount;
    float a_dest = dest.get_a();
    const float one(C::ceil);

    // if a_arc==0.0

    // Scale the source and destination by their alpha values
    src *= a_src;
    dest *= a_dest;

    dest = src + dest * (one - a_src);

    a_dest = a_src + a_dest * (one - a_src);

    // if a_dest!=0.0
    if (fabsf(a_dest) > COLOR_EPSILON) {
        dest /= a_dest;
        dest.set_a(a_dest);
    } else {
        dest = C::alpha();
    }

    assert(dest.is_valid());
    return dest;
}

template <class C>
C blendfunc_STRAIGHT(C &src, C &bg, float amount)
{


    C out;

    float a_out((src.get_a() - bg.get_a())*amount + bg.get_a());

    // if a_out!=0.0
    if (fabsf(a_out) > COLOR_EPSILON) {
        out = ((src * src.get_a() - bg * bg.get_a()) * amount + bg * bg.get_a()) / a_out;
        out.set_a(a_out);
    } else {
        out = C::alpha();
    }

    assert(out.is_valid());
    return out;
}

template <class C>
C blendfunc_ONTO(C &a, C &b, float amount)
{
    float alpha(b.get_a());
    const float one(C::ceil);
    return blendfunc_COMPOSITE(a, b.set_a(one), amount).set_a(alpha);
}

template <class C>
C blendfunc_STRAIGHT_ONTO(C &a, C &b, float amount)
{
    a.set_a(a.get_a()*b.get_a());
    return blendfunc_STRAIGHT(a, b, amount);
}

template <class C>
C blendfunc_BRIGHTEN(C &a, C &b, float amount)
{
    const float alpha(a.get_a()*amount);

    if (b.get_r() < a.get_r()*alpha) {
        b.set_r(a.get_r()*alpha);
    }

    if (b.get_g() < a.get_g()*alpha) {
        b.set_g(a.get_g()*alpha);
    }

    if (b.get_b() < a.get_b()*alpha) {
        b.set_b(a.get_b()*alpha);
    }

    return b;
}

template <class C>
C blendfunc_DARKEN(C &a, C &b, float amount)
{
    const float alpha(a.get_a()*amount);
    const float one(C::ceil);

    if (b.get_r() > (a.get_r() - one)*alpha + one) {
        b.set_r((a.get_r() - one)*alpha + one);
    }

    if (b.get_g() > (a.get_g() - one)*alpha + one) {
        b.set_g((a.get_g() - one)*alpha + one);
    }

    if (b.get_b() > (a.get_b() - one)*alpha + one) {
        b.set_b((a.get_b() - one)*alpha + one);
    }

    return b;
}

template <class C>
C blendfunc_ADD(C &a, C &b, float amount)
{
    float ba(b.get_a());
    float aa(a.get_a()*amount);
    const float alpha(std::max(0.f, std::min(1.f, ba + aa)));
    const float k = fabs(alpha) > 1e-8 ? 1.0 / alpha : 0.0;
    aa *= k;
    ba *= k;

    b.set_r(b.get_r()*ba + a.get_r()*aa);
    b.set_g(b.get_g()*ba + a.get_g()*aa);
    b.set_b(b.get_b()*ba + a.get_b()*aa);
    b.set_a(alpha);

    return b;
}

template <class C>
C blendfunc_SUBTRACT(C &a, C &b, float amount)
{
    float ba(b.get_a());
    float aa(a.get_a()*amount);
    const float alpha(std::max(0.f, std::min(1.f, ba + aa)));
    const float k = fabs(alpha) > 1e-8 ? 1.0 / alpha : 0.0;
    aa *= k;
    ba *= k;

    b.set_r(b.get_r()*ba - a.get_r()*aa);
    b.set_g(b.get_g()*ba - a.get_g()*aa);
    b.set_b(b.get_b()*ba - a.get_b()*aa);
    b.set_a(alpha);

    return b;
}

template <class C>
C blendfunc_DIFFERENCE(C &a, C &b, float amount)
{
    float ba(b.get_a());
    float aa(a.get_a()*amount);
    const float alpha(std::max(0.f, std::min(1.f, ba + aa)));
    const float k = fabs(alpha) > 1e-8 ? 1.0 / alpha : 0.0;
    aa *= k;
    ba *= k;

    b.set_r(std::abs(b.get_r()*ba - a.get_r()*aa));
    b.set_g(std::abs(b.get_g()*ba - a.get_g()*aa));
    b.set_b(std::abs(b.get_b()*ba - a.get_b()*aa));
    b.set_a(alpha);

    return b;
}

template <class C>
C blendfunc_MULTIPLY(C &a, C &b, float amount)
{
    if (amount < 0) {
        a = ~a, amount = -amount;
    }

    amount *= a.get_a();
    b.set_r(((b.get_r()*a.get_r()) - b.get_r()) * (amount) + b.get_r());
    b.set_g(((b.get_g()*a.get_g()) - b.get_g()) * (amount) + b.get_g());
    b.set_b(((b.get_b()*a.get_b()) - b.get_b()) * (amount) + b.get_b());
    return b;
}

template <class C>
C blendfunc_DIVIDE(C &a, C &b, float amount)
{
    amount *= a.get_a();

    // We add COLOR_EPSILON in order to avoid a divide-by-zero condition.
    // This causes DIVIDE to bias toward positive values, but the effect is
    // really negligible. There is a reason why we use COLOR_EPSILON--we
    // want the change to be imperceptible.

    b.set_r(((b.get_r() / (a.get_r() + COLOR_EPSILON)) - b.get_r()) * (amount) + b.get_r());
    b.set_g(((b.get_g() / (a.get_g() + COLOR_EPSILON)) - b.get_g()) * (amount) + b.get_g());
    b.set_b(((b.get_b() / (a.get_b() + COLOR_EPSILON)) - b.get_b()) * (amount) + b.get_b());

    return b;
}

template <class C>
C blendfunc_COLOR(C &a, C &b, float amount)
{
    C temp(b);
    temp.set_uv(a.get_u(), a.get_v());
    return (temp - b) * amount * a.get_a() + b;
}

template <class C>
C blendfunc_HUE(C &a, C &b, float amount)
{
    C temp(b);
    temp.set_hue(a.get_hue());
    return (temp - b) * amount * a.get_a() + b;
}

template <class C>
C blendfunc_SATURATION(C &a, C &b, float amount)
{
    C temp(b);
    temp.set_s(a.get_s());
    return (temp - b) * amount * a.get_a() + b;
}

template <class C>
C blendfunc_LUMINANCE(C &a, C &b, float amount)
{
    C temp(b);
    temp.set_y(a.get_y());
    return (temp - b) * amount * a.get_a() + b;
}

template <class C>
C blendfunc_BEHIND(C &a, C &b, float amount)
{
    if (a.get_a() == 0) {
        a.set_a(COLOR_EPSILON * amount);    // < TODO:  this is a hack
    } else {
        a.set_a(a.get_a()*amount);
    }

    return blendfunc_COMPOSITE(b, a, 1.0);
}

template <class C>
C blendfunc_ALPHA_BRIGHTEN(C &a, C &b, float amount)
{
    // TODO:  can this be right, multiplying amount by *b*'s alpha?
    // compare with blendfunc_BRIGHTEN where it is multiplied by *a*'s
    if (a.get_a() < b.get_a()*amount) {
        return a.set_a(a.get_a() * amount);
    }

    return b;
}

template <class C>
C blendfunc_ALPHA_DARKEN(C &a, C &b, float amount)
{
    if (a.get_a()*amount > b.get_a()) {
        return a.set_a(a.get_a() * amount);
    }

    return b;
}

template <class C>
C blendfunc_SCREEN(C &a, C &b, float amount)
{
    const float one(C::ceil);

    if (amount < 0) {
        a = ~a, amount = -amount;
    }

    a.set_r(one - (one - a.get_r()) * (one - b.get_r()));
    a.set_g(one - (one - a.get_g()) * (one - b.get_g()));
    a.set_b(one - (one - a.get_b()) * (one - b.get_b()));

    return blendfunc_ONTO(a, b, amount);
}

template <class C>
C blendfunc_OVERLAY(C &a, C &b, float amount)
{
    const float one(C::ceil);

    if (amount < 0) {
        a = ~a, amount = -amount;
    }

    C rm;
    rm.set_r(b.get_r()*a.get_r());
    rm.set_g(b.get_g()*a.get_g());
    rm.set_b(b.get_b()*a.get_b());

    C rs;
    rs.set_r(one - (one - a.get_r()) * (one - b.get_r()));
    rs.set_g(one - (one - a.get_g()) * (one - b.get_g()));
    rs.set_b(one - (one - a.get_b()) * (one - b.get_b()));

    C& ret(a);

    ret.set_r(a.get_r()*rs.get_r() + (one - a.get_r())*rm.get_r());
    ret.set_g(a.get_g()*rs.get_g() + (one - a.get_g())*rm.get_g());
    ret.set_b(a.get_b()*rs.get_b() + (one - a.get_b())*rm.get_b());

    return blendfunc_ONTO(ret, b, amount);
}

template <class C>
C blendfunc_HARD_LIGHT(C &a, C &b, float amount)
{
    const float one(C::ceil);
    const float half((one - C::floor) / 2);

    if (amount < 0) {
        a = ~a, amount = -amount;
    }

    if (a.get_r() > half)	{
        a.set_r(one - (one - (a.get_r() * 2 * one - one)) * (one - b.get_r()));
    } else {
        a.set_r(b.get_r() * (a.get_r() * 2 * one));
    }

    if (a.get_g() > half)	{
        a.set_g(one - (one - (a.get_g() * 2 * one - one)) * (one - b.get_g()));
    } else {
        a.set_g(b.get_g() * (a.get_g() * 2 * one));
    }

    if (a.get_b() > half)	{
        a.set_b(one - (one - (a.get_b() * 2 * one - one)) * (one - b.get_b()));
    } else {
        a.set_b(b.get_b() * (a.get_b() * 2 * one));
    }

    return blendfunc_ONTO(a, b, amount);
}

template <class C>
C blendfunc_ALPHA_OVER(C &a, C &b, float amount)
{
    const float one(C::ceil);
    C rm(b);

    // multiply the inverse alpha channel with the one below us
    rm.set_a((one - a.get_a())*b.get_a());

    return blendfunc_STRAIGHT(rm, b, amount);
}

} // synfig namespace

#endif // __SYNFIG_COLOR_COLORBLENDINGFUNCTIONS_H